[kotlin][client] Register enumUnknownDefaultCase as a CliOption so it appears in config-help and the docs - #24977
Conversation
enumUnknownDefaultCase has worked for the kotlin generator since it shipped in v5.3.1, but has never been advertised: it appears in neither `config-help -g kotlin` nor docs/generators/kotlin.md, so the only way to discover it is to read the generator source. Root cause: AbstractKotlinCodegen.java:308 calls cliOptions.clear() and re-adds only Kotlin-specific options, discarding the registration DefaultCodegen.java:2190 makes. DefaultCodegen.processOpts() still reads the key back from additionalProperties (DefaultCodegen.java:449), so the flag stayed functional while invisible. The clear() predates the flag by nearly four years (7cad47d, 2018-01-27 vs c305c71, 2021-12-21), so Kotlin was never deliberately excluded. Registered on KotlinClientCodegen rather than AbstractKotlinCodegen because kotlin is the only Kotlin generator whose templates implement the fallback; kotlin-spring, kotlin-server, kotlin-vertx, kotlin-misk and kotlin-wiremock have zero references to it. Mirrors GoClientCodegen, which re-registers this same option after AbstractGoCodegen's clear(). docs/generators/kotlin.md regenerated with bin/utils/export_docs_generators.sh. No sample changes: the default is unchanged at false. Refs OpenAPITools#12970 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Adds a regression test asserting the option is present in KotlinClientCodegen.cliOptions(). The defect being fixed is an option silently disappearing from cliOptions, so without a test the same thing can happen again on the next refactor. Verified it discriminates: with the registration reverted it fails with "enumUnknownDefaultCase is not registered". Also shortens the `true` value description to "Each enum gains an `unknown_default_open_api` case." The previous wording promised that unrecognized values decode to it, which contradicts the option description directly above it - gson and multiplatform emit the case without decoding to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CliOption.newBoolean(opt, description) already delegates to the 3-arg overload with false, so .defaultValue(Boolean.FALSE.toString()) set what was already set. Matches this constructor's own convention: USE_JACKSON_3 and USE_NON_ASCII_HEADERS omit it, USE_RESPONSE_AS_RETURN_TYPE passes true explicitly because true is not the default. Docs output is unchanged. The regression test still asserts the default is false, so it now covers the implicit path too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rary
The first wording claimed jackson and kotlinx_serialization decode unknown
values to the fallback, and that gson and multiplatform "fail". Reading the
templates, all three claims are wrong:
- enum_class.mustache gates the jackson `decode()` fallback on {{^isNullable}},
so a nullable enum returns null. `decode()` carries @JsonCreator, so Jackson
routes through it and never reaches the @JsonEnumDefaultValue /
READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE path Serializer.kt configures.
- The kotlinx_serialization KSerializer falls back only under {{#isString}};
{{^isString}} throws IllegalArgumentException, so integer enums still throw.
- gson registers no enum adapter, so Gson's built-in returns null rather than
failing - quieter than "fail" implies, and worse, since null is
indistinguishable from an absent field.
Also annotates `multiplatform` as (library) rather than leaving it inside a
(serializationLibrary) clause - it is a library value, and line 300 of this
file sets the convention of tagging each name with its own option.
Test javadoc said the guard stops the option disappearing "again"; it never
disappeared, it was never registered. Reworded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Set.of is already used across the test tree, including the kotlin package, and the target is Java 11. Drops the Arrays and HashSet imports. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Ready for review — everything is green on @wing328 — would this be considered for the 7.26.0 milestone (due 24 Sep)? It is small and low-risk: one The user-visible effect is that Happy to adjust anything — scope, wording of the description, or the test — whatever makes it easiest to take. |
|
just merged. thanks for the enhancement. |
enumUnknownDefaultCasehas worked for thekotlingenerator since it shipped in v5.3.1, but it is advertised nowhere. It appears in neitherconfig-help -g kotlinnordocs/generators/kotlin.md, so the only way to find out it exists is to read the generator source. In practice users conclude the kotlin client does not support enum fallbacks at all.It is not a documentation oversight that can be fixed in the docs.
docs/generators/*.mdis generated from each generator'sCliOptionlist bybin/utils/export_generator.sh(config-help --full-details --format markdown), andbin/utils/ensure-up-to-datere-runs it as a CI gate. The missingconfig-helpentry and the missing docs row are the same bug, and registering the option fixes both.Root cause
AbstractKotlinCodegencallscliOptions.clear()atAbstractKotlinCodegen.java:308and then re-adds only the Kotlin-specific options. That discards the registrationDefaultCodegenmakes atDefaultCodegen.java:2190-2199.DefaultCodegen.processOpts()still reads the key back out ofadditionalPropertiesatDefaultCodegen.java:449, which is why the flag has always been functional — just invisible. Passing--additional-properties=enumUnknownDefaultCase=truetoday produces a correct client; nothing tells you that you can.The
clear()predates the option by nearly four years — it was introduced in7cad47dd399(2018-01-27, "[kotlin-server] --library=ktor"), andenumUnknownDefaultCaselanded inc305c717156(2021-12-21, #11078). Kotlin was never deliberately excluded.Why
KotlinClientCodegenand notAbstractKotlinCodegenkotlinis the only Kotlin generator whose templates implement the fallback. Grepping the template directories forenumUnknownDefaultCase/unknown_default_open_apigives 11 files underkotlin-clientand zero underkotlin-spring,kotlin-server,kotlin-vertx-server,kotlin-misk,kotlin-wiremockandktorm-schema.Registering it on the abstract class would advertise it on generators where enabling it makes the output worse rather than better. The extra enum constant is injected language-neutrally in
DefaultCodegen(injectEnumUnknownDefaultCase), sokotlin-springwith the flag on emits anunknown_default_open_apiconstant into the model's public API whileforValue()still throwsIllegalArgumentExceptionon an unknown value — a phantom constant and the original crash.This mirrors
GoClientCodegen.java:167-177, which re-registers this same option afterAbstractGoCodegen's owncliOptions.clear();docs/generators/go.mdhas the row and the other Go generators do not.Serialization-library support is partial, and the description says so
Support is partial in ways that are easy to get wrong, so the description names each gap rather than implying blanket support:
For reference, where each stands in the
kotlin-clienttemplates today:moshi— full.SerializerHelper.ktregistersEnumJsonAdapter.create(...).withUnknownFallback(...)for every enum, wired in fromSerializer.kt.mustache.jackson— non-nullable enums only.enum_class.mustachegates thedecode()fallback on{{^isNullable}}; a nullable enum falls through toentries.firstOrNull { ... }and yieldsnull. Becausedecode()carries@JsonCreator, Jackson routes through it and never reaches the@JsonEnumDefaultValue/READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUEpath thatSerializer.kt.mustachesets up.kotlinx_serialization— string enums only. The generatedKSerializeremits the?: <fallback>under{{#isString}}and?: throw IllegalArgumentException("Unknown enum value: $value")under{{^isString}}, so an integer enum still throws. (Inline enums viadata_class.mustachedo fall back unconditionally.)gson— none. No enum adapter is registered, so Gson's built-in returnsnullfor an unrecognized name. Quieter than throwing, and arguably worse:nullis indistinguishable from an absent field, and Gson bypasses Kotlin null checks.multiplatform(alibrary, not aserializationLibrary) — none; the serializer throws.The phrasing follows the neighbouring options in the same file,
generateOneOfAnyOfWrappersanduseJackson3, which scope themselves the same way.Related
SerializerHelper.kt.mustacheemitsEnumJsonAdapter.create(...)without.nullSafe(), and because that adapter shadows Moshi's null-safe built-in for every enum in the spec, any model with an optional enum property throws on encode (NullPointerException: value was null! Wrap in .nullSafe()) and on decoding an explicit JSONnull. Since every property absent fromrequiredis a nullable enum, that is most models. Making the option discoverable is worth considerably more once [kotlin] fix: enumUnknownDefaultCase's moshi fallback adapters break nullable enum fields #24894 lands — together they turn a hidden, moshi-fragile flag into a feature users can actually find and rely on. The two changes touch disjoint files and neither needs to rebase on the other.Scope and verification
One source file, one new test, and the regenerated
docs/generators/kotlin.md(+1 line). No behaviour change: the default staysfalseandDefaultCodegenalready initialised the field.bin/utils/ensure-up-to-dateinvokes (bin/meta-codegen.sh,bin/utils/export_docs_generators.sh,bin/utils/copy-to-website.sh,bin/utils/export_generators_readme.sh);export_docs_generators.shregenerates every generator's doc andkotlin.mdwas the only one that moved.bin/configs/kotlin*.yamlandbin/configs/ktorm*.yaml.git status --porcelain samples/is empty.config-help -g kotlinfrom a jar built on this branch now lists the option with the description and both enum values.KotlinClientCodegenModelTest#testEnumUnknownDefaultCaseIsRegisteredAsCliOption, a regression guard for the registration itself. Verified it discriminates: with the registration reverted it fails withenumUnknownDefaultCase is not registered.org.openapitools.codegen.kotlinpackage (KotlinClientCodegenModelTest,KotlinClientCodegenApiTest,AbstractKotlinCodegenTest, the misk/server/spring/vertx tests) plusAllGeneratorsTest, which includes thenoDuplicateCliOptionsguard. Nothing asserts an exact option count, andKotlinMiskServerCodegenOptionsTestis unaffected because the registration is scoped tokotlinrather than the abstract class.Note for maintainers
Five other
DefaultCodegenoptions are dropped the same way for every Kotlin generator:ensureUniqueParams,allowUnicodeIdentifiers,prependFormOrBodyParameters,legacyDiscriminatorBehavioranddisallowAdditionalPropertiesIfNotPresent. I have deliberately left those alone — each needs its own check of whether the Kotlin templates honour it, and a couple are behaviour-bearing. Happy to follow up separately if that is wanted.PR checklist
./mvnw clean package; all four scripts thatbin/utils/ensure-up-to-dateinvokes (bin/meta-codegen.sh,bin/utils/export_docs_generators.sh,bin/utils/copy-to-website.sh,bin/utils/export_generators_readme.sh); and./bin/generate-samples.shover all 128bin/configs/kotlin*.yamlandbin/configs/ktorm*.yaml.git status --porcelain samples/is empty, andkotlin.mdwas the only generator doc that moved. I scoped sample regeneration to Kotlin rather than every language, since the change is confined toKotlinClientCodegen's constructor — happy to run the full sweep if CI disagrees.@jimschubert — flagging you specifically since
7cad47dd399is where thecliOptions.clear()came from. Nothing about this change suggests that was wrong; the option simply did not exist yet, and nothing has re-added it since.🤖 Generated with Claude Code
Summary by cubic
Registers the
enumUnknownDefaultCaseoption for thekotlinclient generator so it appears inconfig-helpanddocs/generators/kotlin.md. The flag has worked since v5.3.1 but was invisible becauseAbstractKotlinCodegenclears the option list, droppingDefaultCodegen's registration; no behavior changes, the default staysfalse.KotlinClientCodegen, the only Kotlin generator whose templates implement the fallback.moshidecodes every unknown value:jacksonskips nullable enums,kotlinx_serializationskips non-string enums, andgsonandmultiplatformemit the case but don't decode to it.docs/generators/kotlin.mdwas regenerated with the repo tooling; no sample changes.Written for commit fed2b66. Summary will update on new commits.